package org.springblade.core.opc;

import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.springblade.core.etl.service.EtlJob;
import org.springblade.core.opc.config.OpcConfig;
import org.springblade.core.tool.utils.PingKit;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class OpcClientPool {

    //客户端缓存
    private static Map<String, OpcUaClient> clientConfigMap = new HashMap<>();
    //客户端缓存，失败次数
    private static Map<String, Integer> errorConnMap = new HashMap<>();

    /**
     * 取得客户端
     *
     * @param opc
     * @param etl
     * @return
     * @throws Exception
     */
    public static synchronized OpcUaClient getClient(OpcConfig opc, EtlJob etl) throws Exception {
        //取得缓存连接
        String serverName = etl.getClass().getName();
        OpcUaClient client = clientConfigMap.get(serverName);
        if (client != null) {
            try {
                //检测可用性
                checkEnable(client);
                //成功了，移除失败次数
                errorConnMap.remove(serverName);
            } catch (Exception e1) {
                log.info("第一次缓存取得opc client 检测不通过，休眠{}秒再次检测", sleep(2));
                try {
                    //检测可用性
                    checkEnable(client);
                    //成功了，移除失败次数
                    log.info("缓存取得opc client 第二次检测通过");
                    errorConnMap.remove(serverName);
                } catch (Exception ex) {
                    log.debug(ex.getMessage(), ex);
                    //关闭连接
                    closeClient(client);
                    log.info("第二次缓存取得opc client 检测不通过，清空缓存，重新申请尝试");
                    client = null;
                    clientConfigMap.remove(serverName);
                }
            }
        }
        //没有可用，则申请
        if (client == null) {
            //取得失败次数
            Integer v = errorConnMap.getOrDefault(serverName, 0);
            try {
                //失败次数多了，先检测网络
                if (v > 2) {
                    String ip = PingKit.getIpFromUrl(opc.getUrl());
                    if (!PingKit.isOk(ip)) {
                        throw new Exception("网络地址" + ip + "不通，不能建立链接");
                    } else {
                        log.info("ping IP:" + ip + "...... 成功");
                    }
                }
                //网络无问题，则创建连接
                client = OpcUaUtil.createClient(opc.getUrl());
                if (client != null) {
                    //缓存连接
                    clientConfigMap.put(serverName, client);
                    //成功了，移除失败次数
                    errorConnMap.remove(serverName);
                    log.info("申请opc client 成功");
                }
            } catch (Exception ex) {
//                log.debug(ex.getMessage(),ex);
                v = v + 1;
                errorConnMap.put(serverName, v);
                if (v > 3) {
                    log.error("已经失败了{}次，尝试次数过多，休眠{}秒", v, sleep(v));
                }
                throw ex;
            }
        }
        return client;
    }


    /**
     * 检测可用性
     *
     * @param client
     * @throws Exception
     */
    private static void checkEnable(OpcUaClient client) throws Exception {
        try {
            client.readNamespaceTable();
            log.debug("读取命名空间成功");
        } catch (Exception ex) {
            log.debug(ex.getMessage(), ex);
            log.error("检测客户端异常：" + ex.getMessage());
            throw ex;
        }
    }

    /**
     * 关闭连接
     *
     * @throws Exception
     */
    private static void closeClient(OpcUaClient client) throws Exception {
        try {
            if (client != null) {
                client.disconnect();
            }
        } catch (Exception ex) {
            log.debug(ex.getMessage(), ex);
        }
    }


    /**
     * 休眠（s）
     *
     * @param retryTimes 重试次数
     * @return
     */
    private static Long sleep(int retryTimes) {
        Long minute = 5L * 60;
        int hour = LocalDateTime.now().getHour();
        try {
            if (hour < 6 || hour > 16) {
                minute = minute * 5;
                Thread.sleep(minute * 1000L);

            } else {
                Thread.sleep(minute * 1000L);
            }
        } catch (Exception ex) {
        }
        return minute;
    }

}
